class Vue {
    constructor(options) {
        this.$options = options
        this._data = options.data
        this.initData()
    }

    initData() {
        let data = this._data
        let keys = Object.keys(data)
        for(let i=0;i<keys.length;i++) {
            Object.defineProperty(this, keys[i], {
                enumerable: true,
                configurable: true,
                get(){
                    return data[keys[i]]
                },
                set(value) {
                    data[keys[i]] = value
                }
            })
        }
        observe(data)
        initWatch(data)
    }
    initWatch() {
        let watch = this.$optons.watch
        if(watch) {
            let keys = Object.keys(watch)
            for(let i=0;i<keys.length;i++){
                new Watcher(this, keys[i], watch[keys[i]])
            }
        }
    }
    $watch(exp, cb) {
        new Watcher(this, exp, cb)
    }
}

function observe(data) {
    let type = Object.prototype.toString.call(data)
    if(type !== '[object Object]' && type !== '[object Array]') return
    new Observer(data)
}

function defineReaction(obj, key, value) {
    observe(obj[key])
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            return value
        },
        set(val) {
            if(val === value) return
            value = val
        }
    })
}

class Observer {
    constructor(data) {
        this.walk(data)
    }
    walk(data) {
        let keys = Object.keys(data)
        for(let i=0;i<keys.length;i++){
            defineReaction(data, keys[i], data[keys[i]])
        }
    }
}

class Dep {
    constructor() {
        this.subs = []
    }
    depend() {
        if(Dep.target) {
            this.subs.push(Dep.target)
        }
    }
    notify() {
        this.subs.forEach(watcher => {
            watcher.run()
        })
    }
}

class Watcher {
    constructor(vm, exp, cb) {
        this.vm = vm
        this.exp = exp
        this.cb = cb
        this.get()
    }
    
    get() {
        Dep.target = this
        this.vm[this.exp]
        Dep.target = undefined
    }

    run() {
        this.cb.call(this.vm)
    }
}